Skip to content

hooks: resolve repo root from the invoking worktree, fail closed - #92

Merged
sumitake merged 1 commit into
mainfrom
dev/claude/hook-worktree-root
Aug 5, 2026
Merged

hooks: resolve repo root from the invoking worktree, fail closed#92
sumitake merged 1 commit into
mainfrom
dev/claude/hook-worktree-root

Conversation

@sumitake

@sumitake sumitake commented Aug 5, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes a wrong-tree bug in the local git-hook gates: .githooks/pre-commit and .githooks/pre-push resolved the repository root from the hook file's own location (SCRIPT_DIR/..). Because core.hooksPath is an absolute path into the primary checkout, any commit or push from a linked session worktree was validated (consistency check, test gate, compliance chain entry) against the primary checkout's tree — stale or dirty with another session's edits — instead of the tree actually being committed. Observed 2026-08-05 as a false NOTICE ... drifted pre-commit FAIL from a worktree whose own tree passed; the same shape can produce false PASSes.

Both wrappers now resolve REPO_ROOT="$(git rev-parse --show-toplevel)" and fail closed when the root cannot be resolved (no cwd fallback — per the Codex cross-family review, a fallback would re-open the exact wrong-tree class this fixes). The pre-push test-gate subshell sanitization and the exec'd compliance checker (hook env + stdin preserved) are structurally unchanged.

Operational note: the fix becomes ACTIVE only when the primary checkout — whose working tree hosts the core.hooksPath copies — is updated to a commit containing it (currently parked on a stale commit with uncommitted edits from another session; not touched by this PR).

Boundary declaration

  • No provider executor source, raw provider command, credential, private absolute path, retired package tree, downloader, or post-install hook is included.
  • Native-runtime changes, if any, contain only a final signed artifact and reviewed public verification metadata; implementation and credentials remain private. (No native-runtime changes.)
  • The change does not create a host-specific preset or provider-specific plugin.

Generated and release surfaces

  • Skill specs and generated SKILL.md files are in parity. (build_skills.py --check OK — untouched.)
  • Claude and Codex marketplaces/manifests are in parity. (build_marketplace.py --check OK — untouched.)
  • A unique changelog.d/ fragment is present for a user-visible change (changelog.d/20260805-hook-worktree-root.md); generated CHANGELOG.md untouched.
  • Version metadata is bumped when behavior or distributed content changes. (N/A — repository tooling only, no distributed content; no bump, per the .githooks pre-push wrapper precedent.)

Verification

  • python3 scripts/build_skills.py --check — OK
  • python3 scripts/build_marketplace.py --check — OK
  • python3 scripts/build-changelog.py --dry-run — OK (--check is release-PR-only under the fragment-only convention)
  • python3 -m unittest discover -s tests -t . -v — Ran 660, OK
  • python3 -m unittest discover -s scripts -p 'test_*.py' -v — Ran 333, OK (327 + 6 new: hook-lives-elsewhere for both wrappers, fail-closed rev-parse for both, branch-detection knob split, real-git linked-worktree integration test)
  • python3 scripts/check_release_consistency.py — RESULT: OK (in the PR worktree)
  • python3 scripts/secret_scan.py — clean
  • python3 scripts/check-public-export-safety.py --active-tree — SAFE (history mode not run: local clone retains pre-rewrite lineage and fails history mode by design; see docs/public-governance.md. No history modified.)
  • git diff --check — clean
  • Live end-to-end: the new pre-commit run from the previously-failing session worktree validates THAT worktree and passes (the old hook produced the false NOTICE FAIL from the same invocation).

Commit made with --no-verify for documented cause: the currently-ACTIVE pre-commit is the primary checkout's pre-fix copy exhibiting exactly the bug this PR fixes; every gate it would run is green in this tree (above) and re-runs authoritatively in CI.

Review and post-condition

Tier 2 (repository validation tooling). Independent cross-family review: Codex (OpenAI, gpt-5.5, effort high, CLI read-only sandbox) — initial verdict DISAGREE-MINOR (confidence H): core root-resolution change endorsed ("the hook file path is irrelevant to Git's repository discovery"); load-bearing recommendation to replace the cwd fallback with fail-closed — integrated (both hooks now hard-fail, with tests). Suggested real-git worktree integration test — integrated. Suggested re-cd'ing the exec'd compliance checker under sanitized env — declined with cause: hook-pre-push.py requires the hook's git env and stdin ref data, and resolves its own root from __file__. Post-merge: update the primary checkout so the fixed hooks become active, then a worktree commit exercises the fix in production.

Compliance trace

author: Claude (Anthropic) on behalf of @sumitake
standing_directives: AGENTS.md source boundaries; docs/public-governance.md; adversarial review folded into cross-check per operator directive
tier: 2
cross_check: PROCEED-WITH-MODIFICATIONS — Codex (OpenAI, gpt-5.5) DISAGREE-MINOR/H; fail-closed root resolution and real-git integration test integrated; compliance-checker re-cd suggestion declined with cause (needs hook env + stdin)
post_condition: CI green; primary checkout to be updated so fixed hooks activate; worktree commit then validates its own tree
mcp_coverage_gap: NONE — no MCP tool surface changes
contributor_rights: OWNER-AUTHORED
operator_reserved: no

🤖 Generated with Claude Code

.githooks/pre-commit and .githooks/pre-push resolved the repository root
from the hook file's own location; with core.hooksPath an absolute path
into the primary checkout, worktree commits/pushes were validated against
the primary checkout's stale/dirty tree (false NOTICE-drift FAIL observed
2026-08-05; false PASSes possible). Root now comes from git rev-parse
--show-toplevel, failing closed when unresolvable (Codex cross-family
review: a cwd fallback re-opens the wrong-tree class). Adds wrapper tests
for the hook-lives-elsewhere topology, fail-closed behavior, and a
real-git linked-worktree integration test.

Pre-commit hook bypassed with --no-verify: the ACTIVE hook is still the
primary checkout's pre-fix copy exhibiting exactly this bug; all gates
run green in this tree and CI re-runs them authoritatively.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@sumitake
sumitake merged commit 465c70e into main Aug 5, 2026
15 checks passed
@sumitake
sumitake deleted the dev/claude/hook-worktree-root branch August 5, 2026 22:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant